package xyz.scootaloo.kami.server.controller

import io.vertx.core.MultiMap
import io.vertx.ext.web.FileUpload
import io.vertx.ext.web.Router
import io.vertx.ext.web.RoutingContext
import xyz.scootaloo.kami.server.controller.dto.HttpMessageResult
import xyz.scootaloo.kami.server.controller.dto.SC
import xyz.scootaloo.kami.server.controller.dto.UploadFileInfo
import xyz.scootaloo.kami.server.controller.dto.UploadPermissionCheck
import xyz.scootaloo.kami.server.standard.*
import xyz.scootaloo.kami.server.service.FileSystemViewService
import xyz.scootaloo.kami.server.service.FileUploadHelper
import xyz.scootaloo.kami.server.service.UploadService
import xyz.scootaloo.kami.server.service.UploadService.UploadFileChunk

/**
 * @author flutterdash@qq.com
 * @since 2022/1/9 16:39
 */
object UploadController : BaseController(), FileUploadHelper {
    private val viewService = FileSystemViewService()
    private val uploadService = UploadService()

    override val log by lazy { getLogger() }
    override val mountPoint = "/upload"
    override val enableJwt = true

    override fun doRoute(router: Router) = router.apply {

        /**
         * 上传时权限检查, 通过访问这个接口可以查看当前用户是否有上传权限;
         * 如果有权限, 则[HttpMessageResult.success]返回ture, 否则false
         */
        post("/checkPerm") { ctx ->
            val uploadPath = ctx.readBodyAsDTO(UploadPermissionCheck::class)
            ctx.assertUploadPerm(uploadPath.path)
            ctx.rest()
        }

        /**
         * 上传时容量检查, 访问此接口需要具备上传权限
         * 如果容量足够上传所有文件, 则返回一个任务编号
         */
        post("/checkCap") { ctx ->
            val uploadInfo = ctx.readBodyAsDTO(UploadFileInfo::class)
            val decodePath = ctx.assertUploadPerm(uploadInfo.path)
            val taskInfo = viewService.createUploadTask(
                ctx.uid(), ctx.access(decodePath),
                uploadInfo.files.map { it.toDesc() }
            )
            ctx.rest(taskInfo)
        }

        post("/sub-progress") { ctx ->
            val json = ctx.bodyAsJson
            val taskId = json.getString("taskId") ?: throw jsonIncomplete()
            val subTaskId = json.getString("filename") ?: throw jsonIncomplete()
            val subTaskInfo = uploadService.findSubTaskInfo(taskId, subTaskId)
            ctx.rest(subTaskInfo)
        }

        post("/main-progress") { ctx ->
            val json = ctx.bodyAsJson
            val taskId = json.getString("taskId") ?: throw jsonIncomplete()
            val taskResp = uploadService.findExistsTaskInfo(taskId)
            ctx.rest(taskResp)
        }

        patch("/") { ctx ->
            // 检查用户提交文件的格式
            val uploads = ctx.fileUploads()
            if (uploads.isEmpty()) {
                return@patch ctx.restWithCode(SC.REQUEST_LACK_UPLOAD_FILE)
            }
            if (uploads.size > 1) {
                uploads.asyncDelete()
                return@patch ctx.restWithCode(SC.REQUEST_CARRYING_TOO_MANY_FILES)
            }

            val uploadFile = uploads.first()
            val formData = ctx.request().formAttributes()
            val chunkInfo = safeReadAttributes(formData, uploadFile)

            val code = processUploadFile(chunkInfo)
            ctx.restWithCode(code)
        }

    }

    private suspend fun RoutingContext.assertUploadPerm(path: String): String {
        val decodePath = decodeURIComponent(path)
        try {
            viewService.checkUploadPerm(access(decodePath))
            return decodePath
        } catch (e: AccessDeniedException) {
            restWithCode(SC.NO_UPLOAD_PERMISSION)
            throw RuntimeException()
        }
    }

    private fun safeReadAttributes(
        formData: MultiMap, upload: FileUpload
    ): UploadFileChunk {
        try {
            val taskId = formData.get("taskId")!!
            val filename = formData.get("filename")!!
            val chunkedSize = formData.get("chunkedSize")!!.toLong()
            val chunkNum = formData.get("chunkNum")!!.toInt()
            return UploadFileChunk(
                taskId, filename, chunkedSize, chunkNum, upload
            )
        } catch (e: Throwable) {
            upload.asyncDelete()
            throw RequestFormDataIncompleteException()
        }
    }

    private suspend fun processUploadFile(chunk: UploadFileChunk): SC {
        val (code, exception) = try {
            uploadService.handleUploadFileChunk(chunk) to null
        } catch (e: UploadTaskNotExistsException) {
            SC.UPLOAD_TASK_NOT_EXISTS_OR_EXPIRY to e
        } catch (e: FileNotInTaskException) {
            SC.FILE_NOT_EXISTS_IN_TASK to e
        } catch (otherException: Throwable) {
            SC.SERVER_ERROR to otherException
        }

        if (!code.succeeded) {
            chunk.uploadFile.asyncDelete()
            exception?.let { log.internalErrorHandle(it) }
        }

        return code
    }
}